package main

import (
    "bytes"
    "crypto/md5"
    "encoding/base64"
    "flag"
    "fmt"
    "json"
    "os"
    "strings"
    "unicode"
)


type jsonDataType [](map[string]string) // Format of JSON test data.


/*
   Implement Oplop in Go.

   See http://code.google.com/p/oplop/wiki/HowItWorks#Technical_Details for
   the algorithm spec.
*/
func Oplop(nickname string, masterPassword string) string {
    md5Hash := md5.New()

    // Wish there was an easier way to convert a string to []byte.
    _, err := md5Hash.Write(bytes.NewBufferString(masterPassword).Bytes())
    if err != nil {
        panic(err)
    }

    _, err = md5Hash.Write(bytes.NewBufferString(nickname).Bytes())
    if err != nil {
        panic(err)
    }

    hashBytes := make([]byte, 24) // 16 bytes of hash == 24 bytes in base64.
    base64.URLEncoding.Encode(hashBytes, md5Hash.Sum())

    // Wish there was a more direct way to convert []byte to string.
    hash := bytes.NewBuffer(hashBytes).String()

    // Could have used the regexp package.
    digitPos := strings.IndexAny(hash, "0123456789")
    if digitPos == -1 {
        hash = "1" + hash
    } else if digitPos > 8 {
        start := digitPos
        end := digitPos + 1
        for end <= len(hash) {
            if !unicode.IsDigit(int(hash[end])) {
                break
            }
            end++
        }

        hash = hash[start:end] + hash
    }

    return hash[:8]
}


func main() {
    flag.Parse()

    testDataPath := flag.Arg(0)

    // Load test data.
    jsonFile, err := os.Open(testDataPath, os.O_RDONLY, 0666)
    if err != nil {
        panic(fmt.Sprintf("couldn't open %v : %v", testDataPath, err))
    }
    defer jsonFile.Close()

    testData := new(jsonDataType)
    jsonDecoder := json.NewDecoder(jsonFile)

    err = jsonDecoder.Decode(testData)
    if err != nil {
        panic(fmt.Sprintf("error decoding JSON: %v", err))
    }

    for _, data := range *testData {
        nickname := data["label"]
        master := data["master"]
        accountPassword := Oplop(nickname, master)
        expected := data["password"]

        if accountPassword != expected {
            panic(fmt.Sprintf("For %v/%v, %v != %v",
                nickname, master, accountPassword, expected))
        }
    }

    fmt.Println("It's all good!")
}
